/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package binding

import hce.core.datastructure.itemstructure.representation.*
import hce.core.data_types.basic.*
import hce.core.data_types.encapsulated.*
import hce.core.data_types.quantity.*
import hce.core.data_types.quantity.date_time.*
import hce.core.data_types.text.*
import hce.core.data_types.uri.*
import hce.core.support.identification.*
import hce.core.datastructure.itemstructure.*
import hce.core.datastructure.history.*
import hce.core.composition.*
import hce.core.composition.content.*
import hce.core.composition.content.entry.*
import hce.core.composition.content.navigation.*
import hce.core.common.archetyped.*

import org.openehr.am.archetype.Archetype
import org.openehr.am.openehrprofile.datatypes.quantity.*
import org.openehr.am.openehrprofile.datatypes.text.CCodePhrase
import org.openehr.am.openehrprofile.datatypes.quantity.Ordinal
import org.openehr.am.archetype.constraintmodel.*

import org.codehaus.groovy.grails.commons.ApplicationHolder

import java.util.Set
import java.net.URI
import com.thoughtworks.xstream.XStream

import converters.DateConverter // Para formatos de fechas

/**
 *
 * @author leacar21
 */
class FactoryObjectRM {

    private static final INSTANCE = new FactoryObjectRM()
    public static getInstance(){ return INSTANCE }
    private FactoryObjectRM() {}

    //-----------------------------------------------------------------------------

    /**
    *
    * Operación que encapsula el seteo de los atrinutos archetypeNodeId, name y
    * archetypeDetails del los Objetos del RM creados.
    *
    */
    def completarLocatable(Locatable locatable, String archNodeId, Archetype archetype, String tempId){

        //println "ENTRANDO A COMPLETAR LOCATABLE"

        String rmV = ApplicationHolder.application.config.TRAUMAGEN.RMVersion
        String idArquetipo = archetype.archetypeId.value

        // FIXME: si es null o vacio es lo mismo.
        // Creo que no se puede crear un nodo si no tiene id de arquetipo, ver especificacion.
        if(idArquetipo == null){
            idArquetipo = ""
        }
        // FIXME: sacar version de config
        if (rmV == null){
            rmV = "1.0.2"
        }

        Archetyped archDetails = new Archetyped(archetypeId: idArquetipo, templateId: tempId, rmVersion: rmV)

        String nameN
        if (archNodeId) // FIXME: todos los nodos tienen nodeID
        {
            // FIXME: sacar el idioma de la configuracion
            nameN = archetype.ontology.termDefinition("es", archNodeId).getItems().text
        }
        else
        {
            // FIXME
            nameN = "XXX"
        }
        DvText nameNode = new DvText(value: nameN)

        locatable.archetypeNodeId = archNodeId
        locatable.name = nameNode
        locatable.archetypeDetails = archDetails
    }

    //--------------------------------------------------------------------------------------

    /**
    *
    * Operación que encapsula el seteo de los atrinutos encoding, language
    * del los Objetos del Entry creados.
    *
    */
    def completarEntry(Entry e){
        TerminologyID tidE = new TerminologyID(name: "TODO_E", versionId: "") // TODO, Obtenerlo de algún lado
        TerminologyID tidL = new TerminologyID(name: "TODO_L", versionId: "") // TODO, Obtenerlo de algún lado

        e.encoding = new CodePhrase(codeString: "TODO", terminologyId: tidE)
        e.language = new CodePhrase(codeString: "TODO", terminologyId: tidL)
    }

    //--------------------------------------------------------------------------------------

    /**
    * Operación que copia los valores de todos los atributos de rmObjectRootSec
    * a rmObject. Esta operación se utiliza en la operacion completarRMOAS de la clase
    * BindingAOMRM para armar el arbol del RM completo para un template.
    */
    def clonarRMO(Locatable rmObject, Locatable rmObjectRootSec)
    {
        // PAB: con groovy podes pedir los atributos como una lista, iterarlos e irlos seteando por su nombre (te evita hacer el switch).
        //      creo que haciendo rmObject.class.fields te da todos los campos, hay que probar.
        rmObject.archetypeNodeId = rmObjectRootSec.archetypeNodeId
        rmObject.name = rmObjectRootSec.name
        rmObject.archetypeDetails = rmObjectRootSec.archetypeDetails
        Class tipoRM = rmObjectRootSec.getClass()
        switch(tipoRM) {
            case Cluster:
                rmObject.items = rmObjectRootSec.items
                break;
            case Element:
                rmObject.null_flavor = rmObjectRootSec.null_flavor
                rmObject.value = rmObjectRootSec.value
                break;
            case ItemList:
                rmObject.items = rmObjectRootSec.items
                break;
            case ItemSingle:
                rmObject.item = rmObjectRootSec.item
                break;
            case ItemTable:
                rmObject.rows = rmObjectRootSec.rows
                break;
            case ItemTree:
                rmObject.items = rmObjectRootSec.items
                break;
            case CareEntry:
                break;
            case Action:
                rmObject.time = rmObjectRootSec.time
                rmObject.description = rmObjectRootSec.description
                break;
            case Observation:
                rmObject.data = rmObjectRootSec.data
                break;
            case Instruction:
                rmObject.narrative = rmObjectRootSec.narrative
                rmObject.expiryTime = rmObjectRootSec.expiryTime
                rmObject.wfDefinition = rmObjectRootSec.wfDefinition
                rmObject.activities = rmObjectRootSec.activities
                break;
            case Evaluation:
                rmObject.data = rmObjectRootSec.data
                break;
            case Section:
                rmObject.items = rmObjectRootSec.items
                break;
            case Composition:
                rmObject.context = rmObjectRootSec.context
                rmObject.category = rmObjectRootSec.category
                rmObject.territory = rmObjectRootSec.territory
                rmObject.language = rmObjectRootSec.language
                rmObject.content = rmObjectRootSec.content
                break;
            default:
               println "tipoRM no considerado: " + tipoRM.ToString()
               break;
        }
    }

    //--------------------------------------------------------------------------

    /**
    *
    * Operación que crea un objeto del RM "vacio". Utilizada para
    * bindear ArchetypeSlots.
    *
    * FIXME: Locatable es un super-tipo, nunca es utilizado directamente.
    *        ¿desde donde se llama a este metodo? desde BindingAOMRM.bindArchetypeSlot()
    *        pero no se si es correcto llamar solo para crear una instancia vacia...
    *        
    * TODO: necesito el arquetipo que se usa para crear este locatable... para setearle su
    *       archetypeDetails aqui, asi luego puedo saber con que arquetipo se arquetipo y
    *       que nodo se usa para hacerlo.
    */
    //def createLOCATABLE(String tipoRM)
    def createLOCATABLE(String tipoRM, String archNodeId, Archetype arquetipo, String tempId)
    {
        println "|||||||||||||||||||||||||||||||||||||||||||||||||"
        println "|||| CREATE LOCATABLE type: " + tipoRM + " |||||"
        println "|||||||||||||||||||||||||||||||||||||||||||||||||"
        Locatable rmObject
        switch(tipoRM)
        {
            case "CLUSTER":
                rmObject = new Cluster()
            break
            case "ELEMENT":
                rmObject = new Element()
            break
            case "ITEM_LIST":
                rmObject = new ItemList()
                break;
            case "ITEM_SINGLE":
                rmObject = new ItemSingle()
                break;
            case "ITEM_TABLE":
                rmObject = new ItemTable()
            break
            case "ITEM_TREE":
                rmObject = new ItemTree()
            break
            case "CARE_ENTRY":
                rmObject = new CareEntry()
            break
            case "ACTION":
                rmObject = new Action()
            break
            case "OBSERVATION":
                rmObject = new Observation()
            break
            case "INSTRUCTION":
                rmObject = new Instruction()
            break
            case "EVALUATION":
                rmObject = new Evaluation()
            break
            case "SECTION":
                rmObject = new Section()
            break
            case "COMPOSITION":
                rmObject = new Composition()
            break
            default:
               println "tipoRM no considerado: " + tipoRM
            break
        }
    
        completarLocatable(rmObject, archNodeId, arquetipo, tempId)
        
        return rmObject
    }
    
    // -----------------------------
    // data_structure.representation
    // -----------------------------

    def createSECTION(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    {
        Section sec = new Section()
        List<Object> listaItems = listaListRMO[0]
        
        println "--"
        println "--"
        println "-- createSECTION listListRMO: " + listaListRMO
        println "--"
        println "--"
        
        if (listaItems.size() == 0) return null 
        
        listaItems.each{ item ->
        
            if (item)
                sec.addToItems(item)
        }

        completarLocatable(sec, archNodeId, arquetipo, tempId)
        return sec
    }

    def createITEM_TREE(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    {
        // FIXME: si no vienen items, retornar null
        ItemTree itemT = new ItemTree()
        List<Object> listaItems = listaListRMO[0]
    
        //println "---------- ITEM TREE items: " + listaListRMO[0]
    
        // Si no tengo items, no tengo arbol
        if (listaItems.size()==0) return null
    
        listaItems.each{eachItem ->
            if (eachItem != null){
                itemT.addToItems(eachItem)
            }
        }

        completarLocatable(itemT, archNodeId, arquetipo, tempId)
        return itemT
    }

    def createITEM_SINGLE(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    {
        ItemSingle itemSingle = new ItemSingle()
        List<Object> listaItems = listaListRMO[0]
    
        // Si no tengo item, el item single es null
        if (!listaItems[0]) return null
    
        itemSingle.item = listaItems[0]

        completarLocatable(itemSingle, archNodeId, arquetipo, tempId)
        return itemSingle
    }

    def createITEM_LIST(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    {
        List<Object> listaItems = listaListRMO[0]
    
        // Si no tengo items, no tengo lista
        if (listaItems.size()==0) return null
        
        ItemList itemL = new ItemList()
        listaItems.each{ item ->
            if (item)
            {
                if (item instanceof List)
                {
                    item.each{ subItem ->
                        if (subItem)
                            itemL.addToItems(subItem)
                    }
                }
                else{
                    itemL.addToItems(item)
                }
            }
        }

        completarLocatable(itemL, archNodeId, arquetipo, tempId)
        return itemL
    }

    def createITEM_TABLE(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    {
        ItemTable itemT = new ItemTable()
        List<Object> listaItems = listaListRMO[0]
    
        // Si no tengo rows no tengo tabla
        if (listaItems.size()==0) return null
    
        listaItems.each{eachItem ->
            if (eachItem != null){
                itemT.addToRows(eachItem)
            }
        }

        completarLocatable(itemT, archNodeId, arquetipo, tempId)
        return itemT
    }

    //--------------------------------------------------------------------------

    def createEVENT(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    {
        Event e = new Event()
        List<Object> listaItems = listaListRMO[0]
        if (listaItems.size == 1)
        {
            e.data = listaItems[0]
            //e.time = new DvDateTime(value: "20091121") // FIXME: mal fecha
            e.time = new DvDateTime(value: DateConverter.toHL7DateFormat(new Date()) )
        }

        completarLocatable(e, archNodeId, arquetipo, tempId)
        return e
    }

    def createHISTORY(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    {
        History h = new History()
        List<Object> listaEvents = listaListRMO[0]
        
        // Si no tiene eventos, no tengo history
        if (listaEvents.size() == 0) return null
        
        //imprimirObjetoXML(listaEvents)
        
        listaEvents.each{event ->
            if (event){
                h.addToEvents(event)
            }
        }

        // FIXME: mal fecha
        h.origin = new DvDateTime(value: "20091121")
        completarLocatable(h, archNodeId, arquetipo, tempId)
        return h
    }

    def createACTIVITY(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    {
        Activity a = new Activity()
        List<Object> listaItems = listaListRMO[0]
        if (listaItems.size == 1)
        {
            a.description = listaItems[0]
            a.timing = new DvParsable(value: "value", formalism: "formalism") // FIXME
            a.action_archetype_id = arquetipo.archetypeId.value
        }

        completarLocatable(a, archNodeId, arquetipo, tempId)
        return a
    }

    
    //----------------------------------------------------------------------
    // ENTRY
    //----------------------------------------------------------------------

    def createOBSERVATION(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    {
        Observation o = new Observation()
        List<Object> listaItems = listaListRMO[0]
        
        println "--"
        println "--"
        println "-- createOBSERVATION listListRMO: " + listaListRMO
        println "--"
        println "--"
        
        if (listaItems.size() == 0) return null
        
        if (listaItems.size == 1)
        {
            o.data = listaItems[0]
        }

        // FIXME: no puede haber un caso donde vengan 2 elementos, y si lo hay cae aca y deberia tirar except o algo...
        completarLocatable(o, archNodeId, arquetipo, tempId)
        completarEntry(o)
        return o
    }
    
    def createEVALUATION(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    {
        Evaluation e = new Evaluation()
        List<Object> listaItems = listaListRMO[0]
        
        //imprimirObjetoXML(listaItems)
        
        listaItems.each{eachItem ->
            if (eachItem != null){
                e.data = eachItem
            }
        }

        completarLocatable(e, archNodeId, arquetipo, tempId)
        completarEntry(e)
        return e
    }

    def createINSTRUCTION(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    {
        println "== createINSTRUCTION"
        
        // En listaListRMO viene:
        // - Siempre: un DvText que es el narrative
        // - Opcional: lista de Activities
        
        Instruction instruction = new Instruction()
        
        println "~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~~+~+~+~+~"
        println "~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~~+~+~+~+~"
        imprimirObjetoXML(listaListRMO)
        println "~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~~+~+~+~+~"
        println "~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~~+~+~+~+~"
        
        DvText narrative
        List activities
        if (listaListRMO.size()==1) // viene solo el narrative
        {
            narrative = listaListRMO[0][0] // el narrative es el primer elemento de la primer lista
        }
        else // viene narrative y activities
        {
            if (listaListRMO[0] instanceof List) // el primer elemento son las activities
            {
                activities = listaListRMO[0]
                narrative  = listaListRMO[1][0] // el narrative es el primer elemento de la segunda lista
            }
            else // el primer elemento el el narrative
            {
                activities = listaListRMO[1]
                narrative  = listaListRMO[0][0] // el narrative es el primer elemento de la primer lista
            }
        }
        // No tengo otro caso posible
        
        
        instruction.narrative = narrative
        
        activities.each{ activity ->
        
            if (activity)
                instruction.addToActivities(activity)
        }

        completarLocatable(instruction, archNodeId, arquetipo, tempId)
        completarEntry(instruction)
        
        return instruction
    }

    def createACTION(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId){
        Action a = new Action()
        List<Object> listaItems = listaListRMO[0]
        if (listaItems.size == 1){
            a.description = listaItems[0]
            
            // FIXME: mal fecha
            a.time = new DvDateTime(value: "20091121")
        }

        completarLocatable(a, archNodeId, arquetipo, tempId)
        completarEntry(a)
        return a
    }

    def createADMINENTRY(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId){
        AdminEntry ae = new AdminEntry()
        List<Object> listaItems = listaListRMO[0]
        if (listaItems.size == 1){
            ae.protocol = listaItems[0]
        }

        completarLocatable(ae, archNodeId, arquetipo, tempId)
        completarEntry(ae)
        return ae
    }

    //----------------------------------------------------------------------
    // CLUSTER
    def createCLUSTER(List<Object> listaItems, Archetype arquetipo, String archNodeId, String tempId)
    {
        println "??????????????? createCLUSTER"
        println "??????????????? listaItems: " + listaItems
        println "???????????????????????????????????????????????????"
        
        if (listaItems.size()==0) return null
        
        Cluster cl = new Cluster()
        //List<Object> listaElements = listaListRMO[0] // items
        listaItems.each{ item ->
        
            if ( item )
            {
                cl.addToItems(item)
            }
        }

        completarLocatable(cl, archNodeId, arquetipo, tempId)
        return cl
    }

    //--------------------------------------------------------------------------
    //ELEMENT
    //def createELEMENT(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    def createELEMENT(DataValue bindedValue, Archetype arquetipo, String archNodeId, String tempId)
    {
        // PAB:
        // Saque un TRY que era al pedo porque queria tirar una except y tiraba para cartchear
        // y tirar de nuevo, la tiro directamente.
        
        Element e = new Element(value: bindedValue)
        completarLocatable(e, archNodeId, arquetipo, tempId)
        return e
    }

    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------

    // -----------------------------
    // data_types.basic
    // -----------------------------
    
    // DV_IDENTIFIER
    def createDV_IDENTIFIER(pathValor, Archetype arquetipo, String archNodeId, String tempId){
        // TODO
        return new DvIdentifier(assigner: "assigner", code: "code", issuer: "issuer", type: " type")
    }

    // DV_STATE
    def createDV_STATE(pathValor, Archetype arquetipo, String archNodeId, String tempId)
    {
        // TODO
        return new DvState(terminal: false, new DvCodedText(definingCode: new CodePhrase(codeString: "TODO"), value: "TODO" ))
    }

    // -----------------------------
    // data_types.encapsulated
    // -----------------------------

    // DV_ENCAPSULATED
    
    /**
     * org.springframework.web.multipart.MultipartFile es el tipo de los archivos que se suben desde la web.
     * 
     */
    def createDV_MULTIMEDIA (
            org.springframework.web.multipart.MultipartFile file,
            Archetype arquetipo,
            String archNodeId,
            String tempId)
    {
        println "createDV_MULTIMEDIA"
        println "--- file: " + file
        println "======================================================="
        
        
        def mm = new DvMultimedia()
        
        mm.data = new byte[file.size]
        file.inputStream.read( mm.data ) // file -> mm.data
        
        // FIXME: ver los tipos en la terminologia openehr...
        mm.mediaType = new CodePhrase(
                         codeString: file.contentType,
                         terminologyId: new TerminologyID(
                           name: 'openehr'
                         )
                       )
        
        // FIXME: ver si el alternate text esta bien usado
        mm.alternateText = file.originalFilename
        
        mm.size = file.size // tamanio en bytes
        
       
        return mm
    }

    // DV_PARSABLE
    def createDV_PARSABLE(pathValor, Archetype arquetipo, String archNodeId, String tempId)
    {
        // TODO
        return new DvParsable(value: "TODO", formalism: "TODO")
    }

    // -----------------------------
    // data_types.quantyty
    // -----------------------------

    //DV_ABSOLUTE_QUANTITY

    //DV_AMOUNT

    // DV_COUNT
    //def createDV_COUNT(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    // El valor es string y lo tengo que pasar a INT, ahi puedo tirar except por si no tiene formato correcto.
    // El value viene bindeado de createInteger
    def createDV_COUNT(Integer value, Archetype arquetipo, String archNodeId, String tempId)
    {
        if (!value) return null
        return new DvCount(magnitude: value)
    }
    
    /**
     * Si hiciera el FIXME 1 en bindCComplexObject no necesitaria este metodo, tendria un bind que se encarga de crear tipos simples.
     */
    def createInteger(String value, Archetype arquetipo, String archNodeId, String tempId)
    {
        return Integer.parseInt(value)
    }
    
    //def createDV_BOOLEAN(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    // value es el resultado de createDvBoolean
    def createDV_BOOLEAN(Boolean value, Archetype arquetipo, String archNodeId, String tempId)
    {
        return new DvBoolean(value: value)
        /* PAB: no bindea solo crea el objeto.
        if (listaListRMO.flatten() == []){
            return null
        }
        try{
            if (listaListRMO[0].size == 1){
                String val = (String)(listaListRMO[0][0])

                return new DvBoolean(value: Boolean.parseBoolean(val))
            }
            throw new Exception()
        }
        catch (Exception exc){
            throw new Exception("createDV_BOOLEAN con listaListRMO: Colección de listaListRMO no contiene un solo elemento (que debería ser string).")
        }
        */
    }
    
    /**
     * FIXME:
     * Esta mal el nombre del tipo en el archetype parser, en lugar de Boolean pone
     * DvBoolean. Ver que para Integer pone Integer no DvInteger. Ver que de esto
     * hubo una discusion en la mail list hace un tiempo. 
     */
    def createDvBoolean(String value, Archetype arquetipo, String archNodeId, String tempId)
    {
        return Boolean.parseBoolean(value)
    }

    /* PAB: otro createDV_COUNT? ya esta declarado arriba.
    def createDV_COUNT(LinkedHashMap<String, Object> pathValor, Archetype arquetipo, String archNodeId, String tempId){
        try{
            String str = pathValor.find{it.key.endsWith("value")}?.value
            if (str == null){
                return null
            }
            else{
                int mag = Integer.parseInt(str)
                return new DvCount(magnitude: mag)
            }
        }
        catch (Exception exc){
            println exc
            throw new Exception("createDV_COUNT: pathValor no contiene elemento esperado.")
        }
    }
    */

    // DV_INTERVAL
    def createDV_INTERVAL(pathValor, Archetype arquetipo, String archNodeId, String tempId){
        // TODO
        return new DvInterval()
    }

    //DV_ORDERED

    //DV_ORDINAL
    /* no se llama, se llama a createDvOrdinal desde binder.bindCDvOrdinal
    def createDV_ORDINAL(pathValor, Archetype arquetipo, String archNodeId, String tempId){
        // TODO
        TerminologyID tid = new TerminologyID(name: "TODO", versionId: "") // Obtenerlo de algún lado
        CodePhrase cph =  new CodePhrase(codeString: "TODO", terminologyId: tid)

        String val = CtrlTerminologia.getInstance().getTermino(cph.terminologyId, cph.codeString, arquetipo)
        DvCodedText ct = new DvCodedText(definingCode: cph, value: val)

        return new DvOrdinal(value: 1, symbol: ct)
    }
    */
    /* correccion a como deberia ser si se llamara...
    def createDV_ORDINAL(String codeString, Archetype arquetipo, String archNodeId, String tempId)
    {
        // TODO
        TerminologyID tid = new TerminologyID(name: "TODO", versionId: "") // Se saca del arquetipo
        CodePhrase definingCode = new CodePhrase(codeString: codeString, terminologyId: tid)

        // Se deberia invocar el servicio terminologico que hay que hacer :D
        String val = CtrlTerminologia.getInstance().getTermino(cph.terminologyId, cph.codeString, arquetipo)
        DvCodedText ct = new DvCodedText(definingCode: cph, value: val)

        // El valor del ordinal se debe sacar del arquetipo
        return new DvOrdinal(value: 1, symbol: ct)
    }
    */

    // DV_PROPORTION
    def createDV_PROPORTION(pathValor, Archetype arquetipo, String archNodeId, String tempId){
        // TODO
        DvProportion p = new DvProportion()
        return p
    }

    //DV_QUANTIFIED

    // DV_QUANTITY
    def createDV_QUANTITY(pathValor, Archetype arquetipo, String archNodeId, String tempId){
        println "---------------------------------------> Entrando a createDV_QUANTITY(pathValor)"
        // TODO
        DvQuantity q = new DvQuantity()
        return q
    }

    // PROPORTION_KIND
    def createPROPORTION_KIND(pathValor, Archetype arquetipo, String archNodeId, String tempId){
        // TODO
        return new ProportionKind()
    }

    // REFERENCE_RANGE
    def createREFERENCE_RANGE(pathValor, Archetype arquetipo, String archNodeId, String tempId){
        // TODO
        ReferenceRange rr = new ReferenceRange(range: new DvInterval())
        return rr
    }

    // -----------------------------
    // data_types.quantity.date_tyme
    // -----------------------------

    // DV_DATE
    def createDV_DATE(LinkedHashMap<String, Object> pathValor, Archetype arquetipo, String archNodeId, String tempId){

        // Encuentro los valores en la coleccion de path valor
        String year = pathValor.find{it.key.endsWith("year")}?.value
        String month = pathValor.find{it.key.endsWith("month")}?.value
        String day = pathValor.find{it.key.endsWith("day")}?.value

        if ((year != null) && (month != null) && (day != null)){
            // Creo un string con formato ISO 8601
            String fechaISO8601 = crearFechaISO8601(year, month, day, "", "", "")
            return new DvDate(value: fechaISO8601)
        }
        else{
            return null
            //throw new Exception("createDV_DATE: Colección de pathValor no tiene path a 'year' o 'month' o 'day'.")
        }
    }

    def createDV_DATE(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId){

        if (listaListRMO.flatten() == []){
            return null
        }
        try{
            if (listaListRMO[0].size == 1){
                // Creo un string con formato ISO 8601
                String fechaISO8601 = (String)(listaListRMO[0][0])
                return new DvDate(value: fechaISO8601)
            }
            throw new Exception()
        }
        catch (Exception exc){
            throw new Exception("createDV_DATE con listaListRMO: Colección de listaListRMO no contiene un solo elemento (que debería ser string).")
        }
    }

    // DV_DATE_TIME
    def createDV_DATE_TIME(LinkedHashMap<String, Object> pathValor, Archetype arquetipo, String archNodeId, String tempId){

        // Encuentro los valores en la coleccion de path valor
        String year = pathValor.find{it.key.endsWith("year")}?.value
        String month = pathValor.find{it.key.endsWith("month")}?.value
        String day = pathValor.find{it.key.endsWith("day")}?.value
        String hour = pathValor.find{it.key.endsWith("hour")}?.value
        String minute = pathValor.find{it.key.endsWith("minute")}?.value
        String seg = pathValor.find{it.key.endsWith("seg")}?.value

        if ((year != null) && (month != null) && (day != null) && (hour != null)){
            // Creo un string con formato ISO 8601
            String fechaISO8601 = crearFechaISO8601(year, month, day, hour, minute, seg)
            return new DvDateTime(value: fechaISO8601)
        }
        else{
            return null
            //throw new Exception("createDV_DATE_TIME: Colección de pathValor no tiene path a 'year' o 'month' o 'day' u 'hour'.")
        }
    }

    def createDV_DATE_TIME(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId){

        if (listaListRMO.flatten() == []){
            return null
        }
        try{
            if (listaListRMO[0].size == 1){
                // Creo un string con formato ISO 8601
                String fechaISO8601 = (String)(listaListRMO[0][0])
                return new DvDateTime(value: fechaISO8601)
            }
            throw new Exception()
        }
        catch (Exception exc){
            throw new Exception("createDV_DATE_TIME con listaListRMO: Colección de listaListRMO no contiene un solo elemento (que debería ser string).")
        }
    }

    // DV_DURATION
    def createDV_DURATION(pathValor, Archetype arquetipo, String archNodeId, String tempId){
        // TODO
        DvDuration d = new DvDuration()
        return d
    }

    //DV_INTERVAL

    //DV_TEMPORAL

    // DV_TIME
    def createDV_TIME(LinkedHashMap<String, Object> pathValor, Archetype arquetipo, String archNodeId, String tempId){
        // TODO

        // Encuentro los valores en la coleccion de path valor
        String hour = pathValor.find{it.key.endsWith("hour")}?.value
        String minute = pathValor.find{it.key.endsWith("minute")}?.value
        String seg = pathValor.find{it.key.endsWith("seg")}?.value

        if ((year != null) && (month != null) && (day != null) && (hour != null)){
            // Creo un string con formato ISO 8601
            String fechaISO8601 = crearFechaISO8601("", "", "", hour, minute, seg)
            //return new DvDateTime(value: fechaISO8601)
            return new DvTime(value: fechaISO8601)
        }
        else{
            return null
            //throw new Exception("createDV_DATE_TIME: Colección de pathValor no tiene path a 'year' o 'month' o 'day' u 'hour'.")
        }
    }

    def createDV_TIME(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId){
        // TODO

        if (listaListRMO.flatten() == []){
            return null
        }
        try{
            if (listaListRMO[0].size == 1){
                // Creo un string con formato ISO 8601
                String fechaISO8601 = (String)(listaListRMO[0][0])
                return new DvTime(value: fechaISO8601)
            }
            throw new Exception()
        }
        catch (Exception exc){
            throw new Exception("createDV_DATE_TIME con listaListRMO: Colección de listaListRMO no contiene un solo elemento (que debería ser string).")
        }
    }

    // -----------------------------
    // data_types.text
    // -----------------------------

    //CODE_PHRASE
    def createCODE_PHRASE(pathValor, Archetype arquetipo, String archNodeId, String tempId){

        if (pathValor.size() == 0){
            return null
        }
        else if (pathValor.size() == 1){
            String defCode = pathValor.find{it.key.endsWith("defining_code")}?.value
            String[] defCodes = defCode.split(",")

            // Si tiene mas de un valor para la path, retorno una lista de CodePhrase
            if (defCodes.size() > 1){
                LinkedList<CodePhrase> listaCodePhrase = new LinkedList<CodePhrase>()
                defCodes.each{dc ->
                    listaCodePhrase.add(createCodePhrase(ccp, dc.replace(' ',''), arquetipo, archNodeId, tempId))
                }

                return listaCodePhrase
            }
            else{          
                String dc = pathValor.find{it.key.endsWith("defining_code")}?.value
                if (dc != null){
                    return createCodePhrase(ccp, dc, arquetipo, archNodeId, tempId)
                }
                else{
                    return null
                    //throw new Exception("bindCCodePhrase: Colección de pathValor tiene defining_code.")
                }
            }
        }
        else {
            throw new Exception("bindCCodePhrase: Colección de pathValor no tiene 1 elemento.")
        }
    }

    
    def createDV_CODED_TEXT(List<Object> listaListRMO, Archetype arquetipo, String archNodeId, String tempId)
    {
        if (listaListRMO.flatten() == []){
            return null
        }
        //try{
            LinkedList<DvCodedText> listaDvCodedText = new LinkedList<DvCodedText>()

            listaListRMO[0].each{cph ->
                String val = CtrlTerminologia.getInstance().getTermino(cph.terminologyId, cph.codeString, arquetipo)
                listaDvCodedText.add(new DvCodedText(definingCode: cph, value: val))
            }

            return listaDvCodedText
        //}
       // catch (Exception exc){
       //     throw new Exception("createDV_CODED_TEXT: listaListRMO no contiene elemento esperado.")
       // }
    }

    
    def createDV_PARAGRAPH(pathValor, Archetype arquetipo, String archNodeId, String tempId)
    {
        // TODO
        return new DvParagraph()
    }

    //DV_TEXT
    //def createDV_TEXT(pathValor, Archetype arquetipo, String archNodeId, String tempId)
    def createDV_TEXT(String value, Archetype arquetipo, String archNodeId, String tempId)
    {
        //println "   !!!!!!! factory.createDV_TEXT !!!!!!"
        return new DvText(value: value)
        
        // PAB: esto es tarea del binder, la fabrica solo debe crear instancias simples.
        /*
        try{
            String val = pathValor.find{it.key.endsWith("value")}?.value // supongo que habra solo un string asociado a value
            return new DvText(value: val)
        }
        catch (Exception exc){
            return null
            //throw new Exception("createDV_TEXT: listaListRMO no contiene elemento esperado.")
        }
        */
    }


    //TERM_MAPPING
    def createTERM_MAPPING(pathValor, Archetype arquetipo, String archNodeId, String tempId){
        // TODO
        TerminologyID tid = new TerminologyID(name: "TODO", versionId: "") // Obtenerlo de algún lado
        CodePhrase cph =  new CodePhrase(codeString: "TODO", terminologyId: tid)

        return new TermMapping(match: TermMapping.UNKNOWN, target: cph)
    }

    // -----------------------------
    // data_types.time_spesification
    // -----------------------------

    //DV_GENERAL_TIME_SPECIFICATION

    //DV_PERIODIC_TIME_SPECIFICATION
    def createDV_PERIODIC_TIME_SPECIFICATION(){
        // TODO
    }

    //DV_TIME_SPECIFICATION
    def createDV_TIME_SPECIFICATION(){
        // TODO
    }

    // -----------------------------
    // data_types.uri
    // -----------------------------

    // DV_EHR_URI
    def createDV_EHR_URI(pathValor, Archetype arquetipo, String archNodeId, String tempId){
        // TODO
        return new DvEHRURI(value: new URI("ehr.TODO"))
    }

    def createDV_URI(pathValor, Archetype arquetipo, String archNodeId, String tempId){
        // TODO
        return new DvURI(value: new URI("TODO"))
    }


    //-----------------------------------
    // C_DOMAIN_TYPE
    //-----------------------------------

    def createCodePhrase(CCodePhrase ccp, String cs, Archetype arquetipo, String archNodeId, String tempId)
    {
        // Obtengo terminologiId del objeto CCodePhrase que viene por parametro
        TerminologyID tid = new TerminologyID(name: ccp.getTerminologyId().name(), versionId: ccp.getTerminologyId().versionID()) //new TerminologyID(name: "local", versionId: "1.0")
        return new CodePhrase(codeString: cs, terminologyId: tid)
    }

    //-----------------------------------

    def createDvQuantity(double mag, String un, Archetype arquetipo, String archNodeId, String tempId){
        println "---------------------------------------> Entrando a createDvQuantity(double mag, String un)"

        return new DvQuantity(magnitude: mag, units: un)
    }

    //-----------------------------------

    // PAB: que es 's' ?
    // 's' es el codeString que se selecciona en la web como valor.
    def createDvOrdinal(CDvOrdinal cdvo, String s, Archetype arquetipo, String archNodeId, String tempId)
    {
        Set<Ordinal> setOrdinal = cdvo.getList()
        //Ordinal ordin = setOrdinal.find{it.getSymbol().codeString.endsWith(s)}
        Ordinal ordin = setOrdinal.find{it.getSymbol().codeString == s} // la condicion es que sea el mismo codigo.

        TerminologyID ti = new TerminologyID(name: ordin.getSymbol().terminologyId.name, versionId: "")
        CodePhrase cp = new CodePhrase(codeString: s, terminologyId: ti)
        DvCodedText ct = new DvCodedText(value: CtrlTerminologia.getInstance().getTermino(cp.terminologyId, cp.codeString, arquetipo), definingCode: cp)
        int v = ordin.value
        
        return new DvOrdinal(symbol: ct, value: v)
    }

    //-----------------------------------

    void imprimirObjetoXML(Object o){
        println "-----------------................"
        XStream xstream = new XStream();
        String xml = xstream.toXML(o);
        println xml
        println "-----------------................."
    }

    //-----------------------------------
    // Funciones auxiliares
    //-----------------------------------

    /*
     * @author Leandro Carrasco
     *
     * FIXME: o usamos o metodos estaticos o el singleton para no tener 2 criterios distintos para hacer la misma cosa.
     */
    static String crearFechaISO8601(String anio, String mes, String dia, String hora, String min, String seg)
    {
        // FIXME: if (!mes) en lugar de if ((mes != null) && (mes != "")) 
        if ((mes != null) && (mes != "") && (Integer.parseInt(mes) < 10)) mes = "0" + mes
        if ((dia != null) && (dia != "") && (Integer.parseInt(dia) < 10)) dia = "0" + dia
        if ((hora != null) && (hora != "") && (Integer.parseInt(hora) < 10)) hora = "0" + hora
        if ((min != null) && (min != "") && (Integer.parseInt(min) < 10)) min = "0" + min
        if ((seg != null) && (seg != "") && (Integer.parseInt(seg) < 10)) seg = "0" + seg

        if ((dia == null) || (dia == ""))
            dia = "00"
        if ((mes == null) || (mes  == ""))
            mes = "00"
        if ((min == null) || (min == ""))
            min = "00"
        if ((seg == null) || (seg == ""))
            seg = "00"

        if ((hora == null) || (hora == "")) // Date
            return anio + mes + dia
        else if ((anio == null) || (anio == "")) // Time
            return hora + min + seg
        else // DateTime
            return anio + mes + dia + "T" + hora + min + seg
    }
}

